﻿namespace Home.Services.SaleService
{
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Linq;
    using System.Net;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.Http;
    using System.Web.OData;
    using System.Web.OData.Query;
    using System.Web.OData.Routing;
    using Home.Services.SecurityService;

    public class SalesController : BaseODataController
    {
        private readonly SaleDbContext database = new SaleDbContext();

        #region Sale operations

        // GET odata/Sales
        [HttpGet]
        [EnableQuery(PageSize = 50)]
        public IHttpActionResult Get(ODataQueryOptions<Sale> options)
        {
            var sales = this.database.Sales.AsQueryable();
            var expands = options.GetExpandPropertyNames();
            if (expands.Contains("Details")) sales = sales.Include(s => s.Details);
            if (expands.Contains("CustomerContacts")) sales = sales.Include(s => s.CustomerContacts);

            return Ok(sales);
        }

        // GET odata/Sales('id')
        [HttpGet]
        [EnableQuery]
        [ODataRoute("Sales({id})")]
        public Task<IHttpActionResult> Get([FromODataUri] Guid id, ODataQueryOptions<Sale> options)
        {
            var sales = this.database.Sales.Where(s => s.Id == id);
            var expands = options.GetExpandPropertyNames();
            if (expands.Contains("Details")) sales = sales.Include(s => s.Details);
            if (expands.Contains("CustomerContacts")) sales = sales.Include(s => s.CustomerContacts);

            return GetODataSingleAsync(sales, options);
        }

        // GET odata/Sales('id')/Property
        [HttpGet]
        [ODataRoute("Sales({id})/NumberText")]
        [ODataRoute("Sales({id})/DayNumber")]
        [ODataRoute("Sales({id})/TotalNumber")]
        [ODataRoute("Sales({id})/SalePersonName")]
        [ODataRoute("Sales({id})/CustomerName")]
        [ODataRoute("Sales({id})/Status")]
        [ODataRoute("Sales({id})/Created")]
        [ODataRoute("Sales({id})/CreatedBy")]
        [ODataRoute("Sales({id})/Modified")]
        [ODataRoute("Sales({id})/ModifiedBy")]
        public async Task<IHttpActionResult> GetProperty([FromODataUri] Guid id)
        {
            var sale = await this.database.Sales.FindAsync(id);
            return GetODataProperty(sale);
        }

        // POST odata/Sales
        [HttpPost]
        public async Task<IHttpActionResult> Post(Sale sale)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            return Created(await CreateAsync(sale));
        }

        // PUT odata/Sales('id')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPut]
        [ODataRoute("Sales({id})")]
        public Task<IHttpActionResult> Put([FromODataUri] Guid id, Sale sale, ODataQueryOptions<Sale> options)
        {
            if (!ModelState.IsValid)
            {
                return Task.FromResult((IHttpActionResult)BadRequest(ModelState));
            }

            if (id != sale.Id)
            {
                return Task.FromResult((IHttpActionResult)BadRequest("The Id of sale does not match the id"));
            }

            var sales = this.database.Sales.Where(r => r.Id == id);

            return PutOrPatchODataAsync(sales, options,
                () => CreateAsync(sale),
                () => UpdateAsync(sale));
        }

        // PATCH odata/Sales('id')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPatch]
        [ODataRoute("Sales({id})")]
        public Task<IHttpActionResult> Patch([FromODataUri] Guid id, Delta<Sale> patch, ODataQueryOptions<Sale> options)
        {
            if (!ModelState.IsValid)
            {
                return Task.FromResult((IHttpActionResult)BadRequest(ModelState));
            }

            var sales = this.database.Sales.Where(r => r.Id == id);

            return PutOrPatchODataAsync(sales, options,
                () => CreatePatchAsync(patch),
                async () => await UpdatePatchAsync(await sales.FirstAsync(), patch));
        }

        // DELETE odata/Sales('id')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpDelete]
        [ODataRoute("Sales({id})")]
        public Task<IHttpActionResult> Delete([FromODataUri] Guid id, ODataQueryOptions<Sale> options)
        {
            var sales = this.database.Sales.Where(r => r.Id == id);
            return DeleteODataAsync(sales, options, () => DeleteAsync(sales.First()));
        }

        #endregion

        #region Sale detail operations

        // GET odata/Sales('id')/Details
        [HttpGet]
        [EnableQuery(PageSize = 50)]
        [ODataRoute("Sales({id})/Details")]
        public IHttpActionResult GetSaleDetails([FromODataUri] Guid id, ODataQueryOptions<SaleDetail> options)
        {
            var details = this.database.SaleDetails.Where(r => r._SaleId == id);
            return Ok(details);
        }

        // GET odata/Sales('id')/Details('detailId')
        [HttpGet]
        [ODataRoute("Sales({id})/Details({detailId})")]
        public Task<IHttpActionResult> GetSaleDetail([FromODataUri] Guid id, [FromODataUri] Guid detailId, ODataQueryOptions<SaleDetail> options)
        {
            var details = this.database.SaleDetails.Where(r => r.Id == detailId && r._SaleId == id);
            return GetODataSingleAsync(details, options);
        }

        // GET odata/Sales('id')/Details('detailId')/Property
        [HttpGet]
        [ODataRoute("Sales({id})/Details({detailId})/ProductName")]
        [ODataRoute("Sales({id})/Details({detailId})/ProductDescription")]
        [ODataRoute("Sales({id})/Details({detailId})/Quantity")]
        [ODataRoute("Sales({id})/Details({detailId})/UnitPrice")]
        [ODataRoute("Sales({id})/Details({detailId})/Status")]
        [ODataRoute("Sales({id})/Details({detailId})/Created")]
        [ODataRoute("Sales({id})/Details({detailId})/CreatedBy")]
        [ODataRoute("Sales({id})/Details({detailId})/Modified")]
        [ODataRoute("Sales({id})/Details({detailId})/ModifiedBy")]
        public async Task<IHttpActionResult> GetSaleDetailProperty([FromODataUri] Guid id, [FromODataUri] Guid detailId)
        {
            var detail = await this.database.SaleDetails.FindAsync(detailId);
            if (detail == null || detail._SaleId != id)
            {
                return NotFound();
            }

            return GetODataProperty(detail);
        }

        // POST odata/Sales('id')/Details
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPost]
        [ODataRoute("Sales({id})/Details")]
        public async Task<IHttpActionResult> PostSaleDetail([FromODataUri] Guid id, SaleDetail detail)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            return Created(await CreateAsync(id, detail));
        }

        // PUT odata/Sales('id')/Details('detailId')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPut]
        [ODataRoute("Sales({id})/Details({detailId})")]
        public Task<IHttpActionResult> PutSaleDetail([FromODataUri] Guid id, [FromODataUri] Guid detailId, SaleDetail detail, ODataQueryOptions<SaleDetail> options)
        {
            if (!ModelState.IsValid)
            {
                return Task.FromResult((IHttpActionResult)BadRequest(ModelState));
            }

            if (detailId != detail.Id)
            {
                return Task.FromResult((IHttpActionResult)BadRequest("The Id of sale detail does not match the detailId"));
            }

            var details = this.database.SaleDetails.Where(r => r.Id == detailId && r._SaleId == id);

            return PutOrPatchODataAsync(details, options,
                () => CreateAsync(id, detail),
                () => UpdateAsync(id, detail));
        }

        // PATCH odata/Sales('id')/Details('detailId')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPatch]
        [ODataRoute("Sales({id})/Details({detailId})")]
        public Task<IHttpActionResult> PatchSaleDetail([FromODataUri] Guid id, [FromODataUri] Guid detailId, Delta<SaleDetail> patch, ODataQueryOptions<SaleDetail> options)
        {
            if (!ModelState.IsValid)
            {
                return Task.FromResult((IHttpActionResult)BadRequest(ModelState));
            }

            var details = this.database.SaleDetails.Where(r => r.Id == detailId && r._SaleId == id);

            return PutOrPatchODataAsync(details, options,
                () => CreatePatchAsync(id, patch),
                async () => await UpdatePatchAsync(id, await details.FirstAsync(), patch));
        }

        // DELETE odata/Sales('id')/Details('detailId')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpDelete]
        [ODataRoute("Sales({id})/Details({detailId})")]
        public Task<IHttpActionResult> DeleteSaleDetail([FromODataUri] Guid id, [FromODataUri] Guid detailId, ODataQueryOptions<SaleDetail> options)
        {
            var details = this.database.SaleDetails.Where(r => r.Id == detailId && r._SaleId == id);
            return DeleteODataAsync(details, options, () => DeleteAsync(id, details.First()));
        }

        #endregion

        #region Customer contact operations

        // GET odata/Sales('id')/CustomerContacts
        [HttpGet]
        [EnableQuery(PageSize = 50)]
        [ODataRoute("Sales({id})/CustomerContacts")]
        public IHttpActionResult GetContacts([FromODataUri] Guid id, ODataQueryOptions<Contact> options)
        {
            var contacts = this.database.Contacts.Where(r => r._SaleId == id);
            return Ok(contacts);
        }

        // GET odata/Sales('id')/CustomerContacts('contactId')
        [HttpGet]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})")]
        public Task<IHttpActionResult> GetContact([FromODataUri] Guid id, [FromODataUri] Guid contactId, ODataQueryOptions<Contact> options)
        {
            var contacts = this.database.Contacts.Where(r => r.Id == contactId && r._SaleId == id);
            return GetODataSingleAsync(contacts, options);
        }

        // GET odata/Sales('id')/CustomerContacts('contactId')/Property
        [HttpGet]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})/Method")]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})/Value")]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})/Created")]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})/CreatedBy")]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})/Modified")]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})/ModifiedBy")]
        public async Task<IHttpActionResult> GetContactProperty([FromODataUri] Guid id, [FromODataUri] Guid contactId)
        {
            var contact = await this.database.Contacts.FindAsync(contactId);
            if (contact == null || contact._SaleId != id)
            {
                return NotFound();
            }

            return GetODataProperty(contact);
        }

        // POST odata/Sales('id')/CustomerContacts
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPost]
        [ODataRoute("Sales({id})/CustomerContacts")]
        public async Task<IHttpActionResult> PostContact([FromODataUri] Guid id, Contact contact)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            return Created(await CreateAsync(id, contact));
        }

        // PUT odata/Sales('id')/CustomerContacts('contactId')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPut]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})")]
        public Task<IHttpActionResult> PutContact([FromODataUri] Guid id, [FromODataUri] Guid contactId, Contact contact, ODataQueryOptions<Contact> options)
        {
            if (!ModelState.IsValid)
            {
                return Task.FromResult((IHttpActionResult)BadRequest(ModelState));
            }

            if (contactId != contact.Id)
            {
                return Task.FromResult((IHttpActionResult)BadRequest("The Id of contact does not match the contactId"));
            }

            var contacts = this.database.Contacts.Where(r => r.Id == contactId && r._SaleId == id);

            return PutOrPatchODataAsync(contacts, options,
                () => CreateAsync(id, contact),
                () => UpdateAsync(id, contact));
        }

        // PATCH odata/Sales('id')/CustomerContacts('contactId')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpPatch]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})")]
        public Task<IHttpActionResult> PatchContact([FromODataUri] Guid id, [FromODataUri] Guid contactId, Delta<Contact> patch, ODataQueryOptions<Contact> options)
        {
            if (!ModelState.IsValid)
            {
                return Task.FromResult((IHttpActionResult)BadRequest(ModelState));
            }

            var contacts = this.database.Contacts.Where(r => r.Id == contactId && r._SaleId == id);

            return PutOrPatchODataAsync(contacts, options,
                () => CreatePatchAsync(id, patch),
                async () => await UpdatePatchAsync(id, await contacts.FirstAsync(), patch));
        }

        // DELETE odata/Sales('id')/CustomerContacts('contactId')
        [ServiceAuthorize(Roles = SecurityServiceHelper.RoleConstants.Manager)]
        [HttpDelete]
        [ODataRoute("Sales({id})/CustomerContacts({contactId})")]
        public Task<IHttpActionResult> DeleteContact([FromODataUri] Guid id, [FromODataUri] Guid contactId, ODataQueryOptions<Contact> options)
        {
            var contacts = this.database.Contacts.Where(r => r.Id == contactId && r._SaleId == id);
            return DeleteODataAsync(contacts, options, () => DeleteAsync(id, contacts.First()));
        }

        #endregion

        #region Helpers

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                this.database.Dispose();
            }

            base.Dispose(disposing);
        }

        private async Task<Sale> CreateAsync(Sale entity)
        {
            var now = DateTimeOffset.UtcNow;
            var user = Thread.CurrentPrincipal.Identity.Name;

            var start = new DateTimeOffset(now.Year, now.Month, now.Day, 0, 0, 0, 0, TimeSpan.Zero);
            var end = new DateTimeOffset(now.Year, now.Month, now.Day, 23, 59, 59, 999, TimeSpan.Zero);
            var dayNumbers = this.database.Sales.Where(s => s.Created >= start && s.Created <= end).Select(s => s.DayNumber);
            if (await dayNumbers.AnyAsync())
            {
                entity.DayNumber = (await dayNumbers.MaxAsync()) + 1;
            }
            else
            {
                entity.DayNumber = 1;
            }
            var totalNumbers = this.database.Sales.Select(s => s.TotalNumber);
            if (await totalNumbers.AnyAsync())
            {
                entity.TotalNumber = (await totalNumbers.MaxAsync()) + 1;
            }
            else
            {
                entity.TotalNumber = 1;
            }
            entity.Status = SaleStatus.Ok;
            InitCreate(entity, now, user);
            entity.NumberText = entity.GenerateSaleNumber();
            this.database.Sales.Add(entity);

            if (entity.CustomerContacts != null)
            {
                foreach (var contact in entity.CustomerContacts)
                {
                    contact._SaleId = entity.Id;
                    InitCreate(contact, now, user);
                    this.database.Contacts.Add(contact);
                }
            }

            if (entity.Details != null)
            {
                foreach (var detail in entity.Details)
                {
                    detail._SaleId = entity.Id;
                    detail.Status = SaleStatus.Ok;
                    InitCreate(detail, now, user);
                    this.database.SaleDetails.Add(detail);
                }
            }

            await this.database.SaveChangesAsync();
            return entity;
        }

        private async Task<Sale> UpdateAsync(Sale entity)
        {
            var now = DateTimeOffset.UtcNow;
            var user = Thread.CurrentPrincipal.Identity.Name;

            InitModify(entity, now, user);
            this.database.Sales.Attach(entity);
            this.database.Entry(entity).State = EntityState.Modified;

            await this.database.SaveChangesAsync();
            return entity;
        }

        private Task<Sale> CreatePatchAsync(Delta<Sale> patch)
        {
            var entity = new Sale();
            patch.Patch(entity);
            return CreateAsync(entity);
        }

        private Task<Sale> UpdatePatchAsync(Sale entity, Delta<Sale> patch)
        {
            patch.Patch(entity);
            return UpdateAsync(entity);
        }

        private async Task DeleteAsync(Sale entity)
        {
            this.database.Sales.Remove(entity);
            await this.database.SaveChangesAsync();
        }

        private async Task<SaleDetail> CreateAsync(Guid id, SaleDetail entity)
        {
            var now = DateTimeOffset.UtcNow;
            var user = Thread.CurrentPrincipal.Identity.Name;

            entity._SaleId = id;
            entity.Status = SaleStatus.Ok;
            InitCreate(entity, now, user);
            this.database.SaleDetails.Add(entity);

            await this.database.SaveChangesAsync();
            return entity;
        }

        private async Task<SaleDetail> UpdateAsync(Guid id, SaleDetail entity)
        {
            var now = DateTimeOffset.UtcNow;
            var user = Thread.CurrentPrincipal.Identity.Name;

            entity._SaleId = id;
            InitModify(entity, now, user);
            this.database.SaleDetails.Attach(entity);
            this.database.Entry(entity).State = EntityState.Modified;

            await this.database.SaveChangesAsync();
            return entity;
        }

        private Task<SaleDetail> CreatePatchAsync(Guid id, Delta<SaleDetail> patch)
        {
            var entity = new SaleDetail();
            patch.Patch(entity);
            return CreateAsync(id, entity);
        }

        private Task<SaleDetail> UpdatePatchAsync(Guid id, SaleDetail entity, Delta<SaleDetail> patch)
        {
            patch.Patch(entity);
            return UpdateAsync(id, entity);
        }

        private async Task DeleteAsync(Guid id, SaleDetail entity)
        {
            this.database.SaleDetails.Remove(entity);
            await this.database.SaveChangesAsync();
        }

        private async Task<Contact> CreateAsync(Guid id, Contact entity)
        {
            var now = DateTimeOffset.UtcNow;
            var user = Thread.CurrentPrincipal.Identity.Name;

            entity._SaleId = id;
            InitCreate(entity, now, user);
            this.database.Contacts.Add(entity);

            await this.database.SaveChangesAsync();
            return entity;
        }

        private async Task<Contact> UpdateAsync(Guid id, Contact entity)
        {
            var now = DateTimeOffset.UtcNow;
            var user = Thread.CurrentPrincipal.Identity.Name;

            entity._SaleId = id;
            InitModify(entity, now, user);
            this.database.Contacts.Attach(entity);
            this.database.Entry(entity).State = EntityState.Modified;

            await this.database.SaveChangesAsync();
            return entity;
        }

        private Task<Contact> CreatePatchAsync(Guid id, Delta<Contact> patch)
        {
            var entity = new Contact();
            patch.Patch(entity);
            return CreateAsync(id, entity);
        }

        private Task<Contact> UpdatePatchAsync(Guid id, Contact entity, Delta<Contact> patch)
        {
            patch.Patch(entity);
            return UpdateAsync(id, entity);
        }

        private async Task DeleteAsync(Guid id, Contact entity)
        {
            this.database.Contacts.Remove(entity);
            await this.database.SaveChangesAsync();
        }

        private static void InitCreate(Entity entity, DateTimeOffset now, string user)
        {
            entity.Id = Guid.NewGuid();
            entity.Created = now;
            entity.CreatedBy = user;
            entity.Modified = now;
            entity.ModifiedBy = user;
        }

        private static void InitModify(Entity entity, DateTimeOffset now, string user)
        {
            entity.Modified = now;
            entity.ModifiedBy = user;
        }

        #endregion
    }
}
